package cn.blnp.net.flowable.event.middle.signal;

import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <h3>中间事件-信号捕获和信号抛出中间事件</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/9 15:54
 */
@Slf4j
@SpringBootTest
public class MidSignalEvent {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;

    /**
     *  <p>
     *      信号中间事件又分为捕获事件和抛出事件两种类型，即信号中间捕获事件和信号中间抛出事件。
     *  </p>
     *  <p>
     *      当流程流转到信号中间捕获事件时会中断并等待触发，直到接收到相应的信号后沿信号中间捕获事件的外出顺序流继续流转。信号事件默认是全局的，与其他事件（如错误事件）不同，
     *   其信号不会在捕获之后被消费。如果存在多个引用了相同信号的事件被激活，即使它们不在同一个流程实例中，当接收到该信号时，这些事件也会被一并触发。
     *  </p>
     *  <p>
     *      当流程流转到信号中间抛出事件时，工作流引擎会直接抛出信号，其他引用了与其相同信号的信号捕获事件都会被触发，信号发出后信号中间抛出事件结束，流程沿其外出顺序流继续流转。
     *   信号中间抛出事件抛出的信号可以被信号开始事件、信号中间捕获事件和信号边界事件订阅处理。事件定义格式如下所示：<br/>
     *   <h3>信号中间捕获事件</h3>
     *   <pre>
     *       {@code
     *          <!-- 定义信号 -->
     *          <signal id="theSignal" name="测试信号" />
     *          <process id="signalIntermediateCatchEventProcess">
     *              <intermediateCatchEvent id="signalIntermediateCatchEvent">
     *                  <!-- 包含signalEventDefinition子元素，代表信号中间捕获事件 -->
     *                  <signalEventDefinition signalRef="theSignal" />
     *              </intermediateCatchEvent>
     *          </process>
     *       }
     *   </pre>
     *   <h3>信号中间抛出事件</h3>
     *   <pre>
     *       {@code
     *          <!-- 定义信号 -->
     *          <signal id="theSignal" name="测试信号" />
     *          <process id="signalIntermediateThrowingEventProcess">
     *              <!-- 定义中间抛出事件 -->
     *              <intermediateThrowEvent id="signalIntermediateThrowEvent">
     *                  <!-- 包含signalEventDefinition子元素，代表信号中间抛出事件 -->
     *                  <signalEventDefinition signalRef="theSignal" flowable:async="false"/>
     *              </intermediateThrowEvent>
     *          </process>
     *       }
     *   </pre>
     *  </p>
     *
     *  <h2 style="color:#000000;">案例演示：</h2>
     *  <hr/>
     *  <p>
     *      该流程为费用报销流程：报销申请提交后，后续有3个并行分支，一个分支会流转到“业务主管确认”用户任务，另外两个分支会流转到信号中间捕获事件，这两个事件会一直等待触发。当“业务主管确认”用户任务
     *   完成后，流程流转到信号中间抛出事件抛出信号，从而触发两个信号中间捕获事件，使另两个分支分别流转到“部门主管审批”用户任务和“财务主管审批”用户任务。<br/>
     *   <b>流程定义文件：</b>{@code flow/midSignalEvent.bpmn20.xml}<br/>
     *   <b>流程预览图：</b><br/>
     *   <img src="https://free4.yunpng.top/2025/05/09/681dc18a254a0.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("中间捕获、抛出事件案例")
                .addClasspathResource("flow/midSignalEvent.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());
        //查询并完成第一个任务
        Task firstTask = taskQuery.singleResult();
        log.info("第一个用户任务为：{}", firstTask.getName());
        taskService.complete(firstTask.getId());
        log.info("报销申请已提交！");
        //查询并完成第二个任务
        Task secondTask = taskQuery.singleResult();
        log.info("第二个用户任务为：{}", secondTask.getName());
        taskService.complete(secondTask.getId());
        //根据流程实例查询任务
        List<Task> tasks = taskQuery.list();
        log.info("当前流程所处的用户任务有：{}", tasks.stream()
                .map(Task::getName)
                .collect(Collectors.joining("，")));

        /**
         *  演示结果：
         * 2025-05-09 16:53:54.328  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 部署流程定义：228428c5-2cb3-11f0-a47a-50ebf6ec8f6d
         * 2025-05-09 16:53:54.331  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 流程定义编号：midSignalEvent:1:22cbe028-2cb3-11f0-a47a-50ebf6ec8f6d
         * 2025-05-09 16:53:54.411  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 第一个用户任务为：报销申请
         * 2025-05-09 16:53:54.473  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 报销申请已提交！
         * 2025-05-09 16:53:54.476  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 第二个用户任务为：业务主管确认
         * 2025-05-09 16:53:54.523  INFO 64460 --- [           main] c.b.n.f.e.middle.signal.MidSignalEvent   : 当前流程所处的用户任务有：部门主管审批，财务主管审批
         **/
    }
}
